﻿// =============================================================================
// TNTTools XMLHelpers.cs
// Copyright (c) 2013 TNTTechnologies Inc.
// =============================================================================

#region

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Data;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.XPath;

#endregion

namespace TNTTools.XML{
    public class XmlAttribute{
        public String Name; // Note: cap name because we don't want to use a property to access this for speed.
        public String Value;
    }

    /// <summary>
    ///     StringDictionary and matching list for quick access, that can be case insensitive
    /// </summary>
    public class XmlAttributes : StringDictionary{
        private List<XmlAttribute> internalCopyAttributes = new List<XmlAttribute>();

        public XmlAttribute this[int index]{
            get{
                return this.internalCopyAttributes[index];
            }
            set{
                throw new NotImplementedException();
            }
        }

        public override void Add(string key, string value){
            base.Add(key, value);
            this.internalCopyAttributes.Add(new XmlAttribute(){ Name = key, Value = value });
        }

        public override void Clear(){
            throw new NotImplementedException();
        }

        public override void CopyTo(Array array, int index){
            throw new NotImplementedException();
        }

        public override void Remove(string key){
            throw new NotImplementedException();
        }
    }

    public class XmlElement{
        public XmlElements ChildElements = new XmlElements();
        public bool IsInitialized = false;
        public String Name;

        public XmlElement Parent = null;
        private XmlAttributes attributes = new XmlAttributes();

        internal XmlAttributes Attributes{
            get{
                return this.attributes;
            }
            set{
                this.attributes = value;
            }
        }
    }

    public class XmlElements : List<XmlElement>{
    }

    public class XmlHelpers{
        public static XmlElement GetElements(string XmlFragement){
            XmlElements xmlElements = new XmlElements();

            byte[] byteArray = Encoding.ASCII.GetBytes(XmlFragement); //todo: optimize me
            MemoryStream stream = new MemoryStream(byteArray); //todo: optimize me
            XmlReader reader = XmlReader.Create(stream);
            XmlElement currXmlElement = null;

            // Parse the file and display each of the nodes.
            while (reader.Read()){
                if (reader.NodeType == XmlNodeType.Element){
                    if (currXmlElement == null){
                        currXmlElement = new XmlElement(){ Name = reader.Name };
                    }

                    // Don't add a child element if we have not read in the attributes
                    // If there is a child move current xmlElement node to child.
                    if (currXmlElement.IsInitialized){
                        XmlElement temp = new XmlElement(){ Name = reader.Name, Parent = currXmlElement };
                        currXmlElement.ChildElements.Add(temp);
                        currXmlElement = temp;
                    }

                    if (reader.HasAttributes == true){
                        while (reader.MoveToNextAttribute()){
                            currXmlElement.Attributes.Add(reader.Name, reader.Value);
                        }
                        // Move the reader back to the element node.
                        reader.MoveToElement();
                    }
                    currXmlElement.IsInitialized = true;
                } else if (reader.NodeType == XmlNodeType.EndElement){
                    if (currXmlElement.Parent != null){
                        // Traverse back up the heirarchy
                        currXmlElement = currXmlElement.Parent;
                    }
                }
            }

            //GetElements( reader, xmlElement);
            return currXmlElement;
        }

        /*
         *  /*if (reader.IsStartElement())
                {
                    int attributes = reader.AttributeCount;
                  
                    if (attributes > 0)
                    {
                        if (currElement.IsInitialized == false)
                        {
                            currElement.Name = reader.Name;
                            currElement.IsInitialized = true;
                        }
                        else
                        {
                            XmlElement temp = new XmlElement();
                            currElement.ChildElements.Add(temp);
                            currElement = temp;
                            currElement.Name = reader.Name;
                            currElement.IsInitialized = true;
                        }
                    }
                    GetAttributes(reader, currElement);

                }*/

        /// <summary>
        ///     Fills in recu
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="element"></param>
        private static void GetElements(XmlReader reader, XmlElement element){
            XmlElement currElement = element;

            while (reader.Read()){
                GetAttributes(reader, currElement);
            }
        }

        private static void GetAttributes(XmlReader reader, XmlElement currElement){
            for (int i = 0; i < reader.AttributeCount; ++i){
                if (reader.IsStartElement()){
                    XmlElement temp = new XmlElement();
                    currElement.ChildElements.Add(temp);
                    temp.Name = reader.Name;
                    GetAttributes(reader, temp);
                } else{
                    reader.MoveToNextAttribute();
                    currElement.Attributes.Add(reader.Name, reader.Value);
                }
            }
        }

        public static XmlElements GetFlattenedElements(string XmlFragement){
            XmlElements xmlElements = new XmlElements();

            byte[] byteArray = Encoding.ASCII.GetBytes(XmlFragement); //todo: optimize me
            MemoryStream stream = new MemoryStream(byteArray); //todo: optimize me
            XmlReader reader = XmlReader.Create(stream);

            while (reader.Read()){
                if (reader.IsStartElement()){
                    int attributes = reader.AttributeCount;
                    XmlElement xmlElement = null;
                    if (attributes > 0){
                        xmlElement = new XmlElement(){ Name = reader.Name };
                        xmlElements.Add(xmlElement);
                    }
                    for (int i = 0; i < reader.AttributeCount; ++i){
                        reader.MoveToNextAttribute();
                        xmlElement.Attributes.Add(reader.Name, reader.Value);
                    }
                }
            }
            return xmlElements;
        }

        public static string GetElementString(string XPath, string XmlFragment){
            XmlDocument xmlDocumentTemp = new XmlDocument();
            xmlDocumentTemp.LoadXml(XmlFragment);
            XPathNavigator nav = xmlDocumentTemp.CreateNavigator();
            XPathNodeIterator iter = nav.Select(XPath);
            StringBuilder results = new StringBuilder();
            results.Append("<root>");
            while (iter.MoveNext()){
                results.Append(iter.Current.OuterXml);
            }
            results.Append("</root>");
            return results.ToString();
        }

        public static DataSet ToDataSet(string xmlFragment){
            XmlElements elements = GetFlattenedElements(xmlFragment);
            DataSet ds = new DataSet();

            ds.Tables.Add();
            // Add header column definiton

            foreach (XmlAttribute attribute in elements[0].Attributes){
                DataColumn col = new DataColumn(attribute.Name);
                ds.Tables[0].Columns.Add(col);
            }

            for (int row = 0; row < elements.Count; ++row){
                ds.Tables[0].Rows.Add();
                for (int col = 0; col < elements[row].Attributes.Count; ++col){
                    ds.Tables[0].Rows[row][elements[row].Attributes[col].Name] = elements[row].Attributes[col].Value;
                }
            }
            return ds;
        }
    }
}